﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Kawanon.Foundation;
using Kawanon.QtHoney;
using QTPlugin.Interop;
using Kawanon.Localization;

namespace Kawanon.Usability.Visualization
{
	public interface ITrailRenderer
	{
		IntPtr WindowHandle { get; set; }

		RenderingInformation RenderingInformation { get; set; }

		void SetOrigin(Point originPos);

		void DrawLine(Point previousPos, Point currentPos);

		void Erase();
	}

	[Serializable]
	public class TrailColorSettings
	{
		//private Dictionary<Keys, Color> m_colors = new Dictionary<Keys, Color>();

		[LocalizedCategory(typeof(TrailColorSettings))]
		[LocalizedDisplayName(typeof(TrailColorSettings), 0)]
		public Color All
		{
			get
			{
				return Color.Empty;
			}
			set
			{
				IList<Keys> keys = GestureExpression.GetSupportedButtons();
				foreach (Keys button in keys)
				{
					this[button] = value;
				}
			}
		}

		[LocalizedCategory(typeof(TrailColorSettings))]
		[LocalizedDisplayName(typeof(TrailColorSettings), 1)]
		[DefaultValue(typeof(Color), "RoyalBlue")]
		public Color RButton { get; set; }

		[LocalizedCategory(typeof(TrailColorSettings))]
		[LocalizedDisplayName(typeof(TrailColorSettings), 2)]
		[DefaultValue(typeof(Color), "Tomato")]
		public Color MButton { get; set; }

		[LocalizedCategory(typeof(TrailColorSettings))]
		[LocalizedDisplayName(typeof(TrailColorSettings), 3)]
		[DefaultValue(typeof(Color), "MediumSeaGreen")]
		public Color XButton1 { get; set; }

		[LocalizedCategory(typeof(TrailColorSettings))]
		[LocalizedDisplayName(typeof(TrailColorSettings), 4)]
		[DefaultValue(typeof(Color), "Chocolate")]
		public Color XButton2 { get; set; }

		public Color this[Keys button]
		{
			get
			{
				switch (button)
				{
					case Keys.RButton:
						return RButton;
					case Keys.MButton:
						return MButton;
					case Keys.XButton1:
						return XButton1;
					case Keys.XButton2:
						return XButton2;
				}
				throw new ArgumentException();
			}
			set
			{
				switch (button)
				{
					case Keys.RButton:
						RButton = value;
						return;
					case Keys.MButton:
						MButton = value;
						return;
					case Keys.XButton1:
						XButton1 = value;
						return;
					case Keys.XButton2:
						XButton2 = value;
						return;
				}
				throw new ArgumentException();
			}
		}
	}

	[Serializable]
	public class RenderingInformation : TrailColorSettings
	{
		[ResourceKey("RenderingInformation_DisplayName")]
		private enum DisplayNames
		{
			SmoothingMode,
			TrailRenderer,
			LineWidth,
			RenderTarget,
		}

		[TypeConverter(typeof(EnumDisplayNameConverter))]
		public enum TrailRenderers
		{
			[LocalizedEnumDisplayName(0)]
			Smoke,
			[LocalizedEnumDisplayName(1)]
			Solid,
			//Custom,	// カスタムを選んだ場合、ClassReference.BrowseFolder を呼んで選択
		}

		public static TrailRenderers RecommendedRenderer
		{
			get
			{
				return TrailRenderers.Smoke;
			}
		}

		private SmoothingMode m_smoothingMode;

		[LocalizedCategory(typeof(RenderingInformation))]
		[LocalizedDisplayName(DisplayNames.SmoothingMode)]
		[DefaultValue(SmoothingMode.None)]
		public SmoothingMode SmoothingMode
		{
			get
			{
				return m_smoothingMode;
			}
			set
			{
				if (value == SmoothingMode.Invalid)
				{
					return;
				}
				m_smoothingMode = value;
			}
		}

		//[TypeConverter(typeof(ExpandableObjectConverter))]
		//public TrailColorSettings TrailColorSettings { get; set; }

		//public string RendererFullName { get; set; }
		//public string RendererAssemblyString { get; set; }

		//public ClassReference RendererClass { get; set; }

		private TrailRenderers m_trailRenderer;

		[LocalizedCategory(typeof(RenderingInformation))]
		[LocalizedDisplayName(DisplayNames.TrailRenderer)]
		[DefaultValue(TrailRenderers.Solid)]
		public TrailRenderers TrailRenderer
		{
			get
			{
				return m_trailRenderer;
			}
			set
			{
				if (value == TrailRenderers.Smoke)
				{
					SmoothingMode = SmoothingMode.AntiAlias;
				}
				else if (value == TrailRenderers.Solid)
				{
					SmoothingMode = SmoothingMode.None;
				}
				m_trailRenderer = value;
			}
		}

		private int m_lineWidth;

		[LocalizedCategory(typeof(RenderingInformation))]
		[LocalizedDisplayName(DisplayNames.LineWidth)]
		[DefaultValue(1)]
		public int LineWidth
		{
			get
			{
				return Math.Max(m_lineWidth, 1);
			}
			set
			{
				m_lineWidth = value;
			}
		}

		private RenderTargets m_renderTarget;

		[LocalizedCategory(typeof(RenderingInformation))]
		[LocalizedDisplayName(DisplayNames.RenderTarget)]
		[DefaultValue(RenderTargets.TopLevelWindow)]
		public RenderTargets RenderTarget
		{
			get
			{
				if (Convert.ToInt32(m_renderTarget) == 0)
				{
					return RenderTargets.TopLevelWindow;
				}
				return m_renderTarget;
			}
			set
			{
				m_renderTarget = value;
			}
		}

		[NonSerialized]
		private Color m_selectedColor;

		[Browsable(false)]
		public Color SelectedColor
		{
			get
			{
				return m_selectedColor;
			}
		}

		public void ChangeColor(Keys button)
		{
			m_selectedColor = this[button];
		}

		public RenderingInformation()
		{
			//TrailColorSettings = new TrailColorSettings();
			TrailRenderer = TrailRenderers.Solid;	// SmoothingMode を切り替えさせる
			//LineWidth = 1;
			RenderTarget = RenderTargets.TopLevelWindow;
			RButton = Color.RoyalBlue;
			MButton = Color.Tomato;
			XButton1 = Color.MediumSeaGreen;
			XButton2 = Color.Chocolate;
		}
	}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	public enum RenderTargets
	{
		[LocalizedEnumDisplayName(1)]
		TopLevelWindow = 1,

		[LocalizedEnumDisplayName(2)]
		View,
	}

	public static class TrailRendererFactory
	{
		public static ITrailRenderer CreateInstance(RenderingInformation info)
		{
			if (info == null)
			{
				throw new NullReferenceException();
			}

			ITrailRenderer render;
			switch (info.TrailRenderer)
			{
				case RenderingInformation.TrailRenderers.Smoke:
					render = new SmokeTrailRenderer();
					break;
				case RenderingInformation.TrailRenderers.Solid:
					render = new SolidTrailRenderer();
					break;
				default:
					throw new ArgumentException();
			}
			render.RenderingInformation = info;
			return render;
		}
	}

	public abstract class LayeredTrailRenderer : ITrailRenderer, IDisposable
	{
		[DllImport("user32.dll")]
		private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

		public IntPtr WindowHandle { get; set; }
		public RenderingInformation RenderingInformation { get; set; }

		protected Form Layer { get { return m_layer; } }

		private Form m_layer;

		private void Init()
		{
			RECT rc = new RECT();
			GetClientRect(WindowHandle, out rc);

			Point pt = Utility.PointToScreen(WindowHandle, new Point(0, 0));

			m_layer = new RenderingLayerForm();
			m_layer.Location = pt;
			m_layer.Size = new Size(rc.Width, rc.Height);
			//m_layer.Paint += new PaintEventHandler(Form_Paint);

			m_layer.Show();
		}

		public virtual void SetOrigin(Point originPos)
		{
			if (m_layer != null)
			{
				m_layer.Dispose();
			}
			Init();
		}

		public virtual void DrawLine(Point previousPos, Point currentPos)
		{
			if (m_layer == null)
			{
				Init();
			}
		}

		public virtual void Erase()
		{
			if (m_layer != null)
			{
				m_layer.Dispose();
			}
			m_layer = null;
		}

		public virtual void Dispose()
		{
			Erase();
		}
	}

	public abstract class BufferedTrailRenderer : LayeredTrailRenderer
	{
		private const uint WM_PRINT = 0x0317;
		private const uint WM_PRINTCLIENT = 0x0318;
		private const uint WM_SETREDRAW = 0x000B;

		private const uint PRF_CHECKVISIBLE = 0x00000001;
		private const uint PRF_NONCLIENT = 0x00000002;
		private const uint PRF_CLIENT = 0x00000004;
		private const uint PRF_ERASEBKGND = 0x00000008;
		private const uint PRF_CHILDREN = 0x00000010;
		private const uint PRF_OWNED = 0x00000020;

		[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
		private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, uint lParam);

		[DllImport("user32.dll")]
		private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

		[DllImport("user32.dll")]
		private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

		private List<Point> m_points = new List<Point>();
		private Bitmap m_bitmap;

		private void AddPoint(Point pt)
		{
			POINT point = new POINT(pt);
			ScreenToClient(WindowHandle, ref point);
			m_points.Add(point.ToPoint());
		}

		protected virtual void InternalDrawLine(Graphics g, Point pt1, Point pt2, int index, int count)
		{
			throw new NotImplementedException();
		}

		protected virtual void InternalRender(Graphics g, IList<Point> points)
		{
			Point p1 = Point.Empty;
			for (int i = 0; i < points.Count; i++)
			{
				Point p2 = points[i];
				if (p1 != Point.Empty)
				{
					InternalDrawLine(g, p1, p2, i, points.Count);
				}
				p1 = p2;
			}
		}

		protected void Render()
		{
			RECT rc = new RECT();
			if (!GetClientRect(Layer.Handle, out rc))
			{
				throw new OperationFailedException("GetClientRect");
				//return;	// ウィンドウは破棄済み？
			}

			if (m_bitmap != null && (m_bitmap.Width != rc.Width || m_bitmap.Height != rc.Height))
			{
				m_bitmap.Dispose();
				m_bitmap = null;
			}
			if (m_bitmap == null)
			{
				m_bitmap = new Bitmap(rc.Width, rc.Height);
			}

			Graphics g = Graphics.FromImage(m_bitmap);
			//NOTE: IEでゴミが残るため
			g.Clear(Layer.BackColor);

			//NOTE: IEでは線がどんどん濃くなる.. (DirectXの仕様？)
			//SendMessage(WindowHandle, WM_PRINTCLIENT, g.GetHdc(), PRF_CLIENT | PRF_ERASEBKGND);
			//g.ReleaseHdc();
			g.SmoothingMode = RenderingInformation.SmoothingMode;

			InternalRender(g, m_points.AsReadOnly());
			g.Dispose();

			// ツールチップなどに消される場合もあるので、毎回描画しなおす
			//InvalidateRect(WindowHandle, IntPtr.Zero, true);

			Graphics g2 = Graphics.FromHwnd(Layer.Handle);
			g2.DrawImage(m_bitmap, 0, 0);
			g2.Dispose();
		}

		public override void SetOrigin(Point originPos)
		{
			base.SetOrigin(originPos);

			m_points.Clear();
			AddPoint(originPos);
		}

		public override void DrawLine(Point previousPos, Point currentPos)
		{
			base.DrawLine(previousPos, currentPos);

			AddPoint(currentPos);
			Render();
		}

		public override void Dispose()
		{
			if (m_bitmap != null)
			{
				// Win32Exception: このコマンドを実行するのに十分な記憶域がありません。
				// の対策として一応入れてみるが、効果があるのか？
				m_bitmap.Dispose();
			}
			base.Dispose();
		}
	}

	public class SmokeTrailRenderer : BufferedTrailRenderer
	{
		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool IsWindow(IntPtr hWnd);

		[DllImport("user32.dll")]
		private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);

		private Timer m_timer = new Timer();
		private int m_alpha;
		private Pen m_pen;

		public SmokeTrailRenderer()
		{
			m_timer.Tick += new EventHandler(Timer_Tick);
			m_timer.Interval = 10;
		}

		private int GlowArgb(byte rgb, double x)
		{
			return (int)Math.Min(rgb + (255 * x), 255);
		}

		private Color GlowColor(Color c, double x)
		{
			//return Color.FromArgb(GlowRgb(c.R, x), GlowRgb(c.G, x), GlowRgb(c.B, x));
			return Color.FromArgb((int)(c.A * (1.0 - x)), c.R, c.G, c.B);
		}

		private void Timer_Tick(object sender, EventArgs e)
		{
			if (Layer != null)
			{
				m_alpha += 1;

				double diff = (double)(m_alpha) / 20;
				Color c = GlowColor(RenderingInformation.SelectedColor, diff);
				m_pen = new Pen(c, RenderingInformation.LineWidth);
				Render();
				m_pen = null;

				if (diff < 1)
				{
					return;
				}
			}

			m_timer.Stop();	// おしまい
			//InvalidateRect(WindowHandle, IntPtr.Zero, true);
			base.Erase();
		}

		protected sealed override void InternalDrawLine(Graphics g, Point pt1, Point pt2, int index, int count)
		{
			Pen pen;
			if (m_pen != null)
			{
				pen = m_pen;
			}
			else
			{
				double diff = (double)index / count;
				diff = Math.Pow(diff, (double)index * 0.6);
				//System.Diagnostics.Debug.Write(diff + " ");

				Color c = GlowColor(RenderingInformation.SelectedColor, diff);
				pen = new Pen(c, RenderingInformation.LineWidth);
			}
			g.DrawLine(pen, pt1, pt2);
		}

		//protected sealed override void InternalRender(Graphics g, IList<Point> points)
		//{
		//    base.InternalRender(g, points);

		//    //System.Diagnostics.Debug.WriteLine("");
		//}

		public sealed override void Erase()
		{
			//InvalidateRect(this.Handle, IntPtr.Zero, true);
			m_alpha = 0;
			m_timer.Start();
		}

		public override void Dispose()
		{
			m_timer.Stop();
			// すぐに削除
			base.Erase();

			if (m_pen != null)
			{
				m_pen.Dispose();
			}
		}
	}

	public class SolidTrailRenderer : LayeredTrailRenderer
	{
		[DllImport("user32.dll")]
		private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);

		public override void DrawLine(Point previousPos, Point currentPos)
		{
			base.DrawLine(previousPos, currentPos);

			Graphics g = Graphics.FromHwnd(Layer.Handle);
			g.SmoothingMode = RenderingInformation.SmoothingMode;

			g.DrawLine(new Pen(RenderingInformation.SelectedColor, RenderingInformation.LineWidth),
				Utility.PointToClient(WindowHandle, previousPos),
				Utility.PointToClient(WindowHandle, currentPos));
		}
	}
}
